﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace ZackFlame.IronBrainFuck.Parser
{
    /// <summary>
    /// Предоставляет методы для парсинга BrainFuck программы.
    /// </summary>
    public static class BrainParser
    {
        const string AllowedChars = "+-><[].,#@";

        internal static readonly BrainOperation[] SingleOperations =
            new BrainOperation[]
            {
                BrainOperation.CycleOpen,
                BrainOperation.CycleClose,
                BrainOperation.Write,
                BrainOperation.Read,
                BrainOperation.SpecialWrite,
                BrainOperation.SpecialRead,
            };

        /// <summary>
        /// Преобразует BrainFuck программу в массив экземпляров BrainAction.
        /// </summary>
        /// <param name="brainFuckProgram">BrainFuck программа.</param>
        /// <returns>Разобранная BrainFuck программа.</returns>
        public static BrainAction[] Parse(string brainFuckProgram)
        {
            // Удаляем лишние символы
            StringBuilder programBuilder = new StringBuilder();
            for (int i = 0; i < brainFuckProgram.Length; i++)
            {
                if (AllowedChars.IndexOf(brainFuckProgram[i]) != -1)
                    programBuilder.Append(brainFuckProgram[i]);
            }
            string program = programBuilder.ToString();

            // Парсим
            List<BrainAction> actions = new List<BrainAction>();
            BrainOperation previous = BrainOperation.None;
            BrainOperation operation = BrainOperation.None;
            uint count = 0;

            for (int i = 0; i < program.Length; i++)
            {
                previous = operation;

                switch (program[i])
                {
                    case '+':
                        operation = BrainOperation.Add;
                        break;
                    case '-':
                        operation = BrainOperation.Sub;
                        break;
                    case '>':
                        operation = BrainOperation.Right;
                        break;
                    case '<':
                        operation = BrainOperation.Left;
                        break;
                    case '[':
                        if (i < program.Length - 2 && program[i + 2] == ']' &&
                            (program[i + 1] == '-' || program[i] == '+'))
                        {
                            operation = BrainOperation.Zero;
                            i += 2;
                        }
                        else
                        {
                            operation = BrainOperation.CycleOpen;
                        }
                        break;
                    case ']':
                        operation = BrainOperation.CycleClose;
                        break;
                    case '.':
                        operation = BrainOperation.Write;
                        break;
                    case ',':
                        operation = BrainOperation.Read;
                        break;
                    case '#':
                        operation = BrainOperation.SpecialWrite;
                        break;
                    case '@':
                        operation = BrainOperation.SpecialRead;
                        break;
                    default:
                        operation = BrainOperation.None;
                        break;
                }

                if (operation != previous)
                {
                    actions.Add(new BrainAction(previous, count));
                    count = 1;
                }
                else
                {
                    if (SingleOperations.Contains(previous))
                        actions.Add(new BrainAction(previous, 1));
                    else
                        count++;
                }
            }

            if (operation != BrainOperation.None)
                actions.Add(new BrainAction(operation, count));

            return actions.ToArray();
        }
    }
}
